數學上,文氏圖常用作集合論的表達工具,在做資料庫查詢的時候,其實也常常使用到這樣的概念,交集、差集、聯集、補集…etc,都是集合代數的運算操作。
簡單的說,文氏圖關注的是集合與集合之間的交集。
集合,在資料庫被當作 Table,而 JOIN 的方式,就是一張表如何連結另外一張表的關係。
在 MySQL 中,JOIN 最常見的方式有:
INNER JOIN
LEFT JOIN == LEFT OUTER JOIN
(相同的東西)RIGHT JOIN == RIGHT OUTER JOIN
(相同的東西)FULL OUTER JOIN
本篇也會在介紹幾個特別的 JOIN:
CROSS JOIN
FULL JOIN = FULL OUTER JOIN
SELF JOIN
UNION
如果 A 表和 B 表可以有某種關聯,則會把他們的雙方都有的關聯做交集 (Intersection),這個交集後,會出現三個區塊 LEFT JOIN
, INNER JOIN
, RIGHT JOIN
,你可以選擇任一個區塊當作主要的集合,帶出另一個集合,像是下表:
不難想像,LEFT JOIN
區塊就是以 LEFT JOIN
存在的元素交集 RIGHT JOIN
,此時要是有東西不存在 LEFT JOIN
這區,也就不會帶出來; 這件事對 RIGHT JOIN
區塊來說亦然,可是 INNER JOIN
則是只針對兩邊的交集部分進行陳列,所以只要兩邊沒有交集,資料也不會出現。 (有關聯的資料才會出現的意思)
為了底下舉例方便,我建立了測試的資料表:
-- A: User (使用者表)
CREATE TABLE users (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
PersonalInfoId INT NOT NULL)
-- B: PersonalInfo (使用者個人資料)
CREATE TABLE PersonalInfo(
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
identity VARCHAR(256),
address VARCHAR(512)
)
現在,你如果確定一個 user
就一定會有一個 personal info
個人資料的資料表對應,就屬於 1 對 1 的映射關係,你就可以使用 INNER JOIN
來進行查詢,一但 user
或 personal info
它不存在各自的表,只要有一個找不到,則這筆資料就不會顯示,必須是兩邊都有才會顯示。 (user 或 personal info 存在缺漏資料時)
現在,對這兩個表填充一下資料
PersonalInfo:
Users:
然後,寫一個 1-1 關係查詢的 JOIN:
SELECT * FROM users u
INNER JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId;
此時因為 2
不存在某一處 (user 2
不在 PersonalInfo),則無法被 INNER JOIN 一對一的找到,所以連顯示都不會,因此,如果要尋找存在性就不可以使用 INNER JOIN,因為你根本找不出兩邊的映射,它也不會顯示**不完整"**的資料,就像下圖。
請參考下圖,如果想要完整的列出 左邊的表 users
,但 Right 右邊
可有可無,或是左右資料量不是 1-1 的大小,則使用 LEFT JOIN
,換句話說,如果要以 users
尋找是否存在一個或更多 personal info
,需要用 users
去 LEFT JOIN PersonalInfo。
比方說,一個使用者有很多訂單,所以要查詢一個使用者的很多筆訂單,就可以使用 LEFT JOIN
來找出多筆。
而 RIGHT
是 LEFT
的對照,只是左右互換,可以把 SELECT * FROM A LEFT JOIN B
, A 跟 B 互換本身就變成 RIGHT JOIN,只是在誰大誰小的問題。
如果說,現在希望檢查兩張表的資料,任一邊 A, B 只要有一邊不存在,都 SHOW 出來這個狀況,請參考下表圖例:
PersonalInfo 少 2, 比 user 多一個 5
Users 有 2,但少一個 5
此時就可以使用 FULL JOIN (mysql 不支援,可使用 LEFT + RIGHT JOIN 的方法) ,或稱為 UNION。
此方法的不同之處在於兩邊資料表都有缺漏資料,或用另一個觀點看,兩邊資料不對稱,用此方法找出兩邊不對稱的地方。
針對上述資料的問題,則寫一段 SQL 呈現這個狀況:
-- LEFT 表為主的資料去查另一張表 (只差在 LEFT JOIN),這會帶出隔壁沒有的資料
SELECT * FROM users u
LEFT JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId
-- UNION 起來
UNION
-- RIGHT 表為主的資料去查令一張表 (只差在 RIGHT JOIN),這也會帶出隔壁沒有的資料
SELECT * FROM users u
RIGHT JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId
上述 SQL 中都各自帶出了隔壁沒有的資料,就容易看出缺失之處,如下圖結果:
所以,整個 FULL JOIN 是全部兩邊集合(不管大小),不是只有中間,如下圖:
備註
顯然,以邏輯來說,Users
, PersonalInfo
不應該隨便缺任一筆資料,友善對待資料,請加上 Foreign Key Check
,您可以少一些白髮。
CREATE TABLE users (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
PersonalInfoId INT NOT NULL,
CONSTRAINT FK_personalInfo FOREIGN KEY(PersonalInfoId) REFERENCES PersonalInfo(id)
)
SELF JOIN,表示自己 JOIN 自己,在圖表中是:
SELF JOIN 要如何示範? 假設 PersonalInfo
的資料中,想要找出同一個地址,且不同人的身分證,見下圖示。
則可以這麼查詢:
SELECT pi1.identity, pi2.identity, pi1.address
FROM PersonalInfo pi1, PersonalInfo pi2 -- 一次查兩表
WHERE pi1.address = pi2.address AND pi1.identity <> pi2.identity; -- 處理兩表關係
地址相同,人不同,得到下方的結果,如下圖。
什麼是笛卡兒乘積 (Cartesian product) ?
最簡單的範例就是 9x9 乘法表,所以先建立一個類似 9x9 乘法表的兩個表:
CREATE TABLE A(
num INT PRIMARY KEY
);
CREATE TABLE B(
num INT PRIMARY KEY
);
在 A, B 表分別填入 1~9 (這裡不小心填到 10):
建立好資料後,現在要直接對 A, B 做 CROSS JOIN:
SELECT A.num as A, B.num as B, A.num * B.num as 乘積 FROM A
CROSS JOIN B
ORDER BY A.num, B.num;
現在,可以清楚知道,CROSS JOIN 就是把兩張表的每一項都 JOIN,而且不指定任何條件,即便 A, B; B, A 交換出現也會 JOIN 每一項,這個也就是笛卡兒乘積 (所有可能的情況)。
備註
使用 CROSS JOIN 產生的笛卡兒乘積絕對會對效能造成很大的負擔。
另外在本篇沒有介紹到的東西是 DISTINCT
,如果你在單一張表中會出現多筆重複的資料,想要按照某個欄位只選出一筆,則可以使用 DISTINCT
。
References:
[1] https://justcode.ikeepstudying.com/2016/08/mysql-%E5%9B%BE%E8%A7%A3-inner-join%E3%80%81left-join%E3%80%81right-join%E3%80%81full-outer-join%E3%80%81union%E3%80%81union-all%E7%9A%84%E5%8C%BA%E5%88%AB/
[2] https://www.chriswirz.com/materialized-views-in-mysql/
[3] https://www.educba.com/joins-in-mysql/
[4] https://www.techagilist.com/mainframe/db2/outer-join-step-by-step-walkthrough-with-examples/
[5] https://stackoverflow.com/questions/13997365/sql-joins-as-venn-diagram
[6] https://www.techonthenet.com/mysql/joins.php
[7] https://www.guru99.com/joins.html